{ "cells": [ { "cell_type": "markdown", "id": "beb6d5ad-50f3-4a00-8ebc-4ecc643b725f", "metadata": {}, "source": [ "# Welcome to `musica`!\n", "\n", "This tutorial will walk you through getting an installation working and running a box model using the `musica` python package.\n", "\n", ">__NOTE:__ We'll use `musica` to refer to the python package developed as part of the larger MUlti-Scale Infrastructure for Chemistry and Aerosols (MUSICA) Project. To learn more about the MUSICA Project, see [here](https://www2.acom.ucar.edu/sections/multi-scale-infrastructure-chemistry-modeling-musica)." ] }, { "cell_type": "markdown", "id": "97112653-19a6-4bf3-8031-0d9977d61992", "metadata": {}, "source": [ "## Installation (optional)" ] }, { "cell_type": "markdown", "id": "a597c729-a7e5-4ebc-81ae-d2e2317dc276", "metadata": {}, "source": [ "You probably arrived at this tutorial through [binder](https://mybinder.org/). If so, __please skip this section__ as we've taken care of installing everything for you.\n", "\n", "If you want to install `musica` locally, it is available from [pypi](https://pypi.org/project/musica/). Below are instructions that will install `musica`'s dependencies alongside all of the dependencies requires for this tutorial\n", "\n", "```bash\n", "pip install musica[tutorial]\n", "```\n", "\n", "We do recommend you install into some virtual environment. We've included instructions for both [`uv`](https://docs.astral.sh/uv/), and [`conda`](https://docs.conda.io/projects/conda/en/latest/user-guide/getting-started.html)\n", "\n", "### uv (recommended)\n", "\n", "```bash\n", "uv python install 3.13\n", "uv venv --python 3.13\n", "\n", "source .venv/bin/activate # macOS/Linux\n", ".venv\\Scripts\\activate # Windows\n", "\n", "uv pip install musica[tutorial] # bash\n", "uv pip install 'musica[tutorial]' # zsh\n", "```\n", "\n", "### Conda\n", "\n", "```bash\n", "conda create --name musica python=3.13\n", "conda activate musica\n", "pip install musica[tutorial] # bash\n", "pip install 'musica[tutorial]' # zsh\n", "```" ] }, { "cell_type": "markdown", "id": "50b3d12f-85ae-4981-92bd-dc0697c796ce", "metadata": {}, "source": [ "### CLI\n", "\n", "The `musica` pacakge comes with a cli tool, `musica-cli`. This can be useful to copy out some of our examples, or printing the version. You can read more about its usage and options [here](https://ncar-musica.readthedocs.io/en/latest/user_guide/cli.html). To verify your installation worked, you can ask it to print the version number\n", "\n", "```bash\n", "musica-cli --version\n", "```\n", "You should see something like\n", "```text\n", "musica 0.15.0 (MICM 3.12.0, TUV-x 0.15.0, CARMA 4.0.0)\n", "```" ] }, { "cell_type": "markdown", "id": "f971c0e1-c7bc-4e8a-a46f-d3d06f23c3b5", "metadata": {}, "source": [ "## Using `musica`\n", "\n", "Let's start off by importing `musica` and getting the versions in python.\n", "\n", "### Version checks" ] }, { "cell_type": "code", "execution_count": 26, "id": "f237ec7f-624c-4106-bf19-3cacc58d3af9", "metadata": {}, "outputs": [], "source": [ "import musica" ] }, { "cell_type": "code", "execution_count": 27, "id": "a56c1271-1c5d-497e-a323-6ba0e4d6e95a", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0.15.0\n" ] } ], "source": [ "print(musica.__version__)" ] }, { "cell_type": "markdown", "id": "ff9b4671-aece-4fca-b2b3-bd410ce4ae8f", "metadata": {}, "source": [ "Each of `musica`'s componenents can be imported as a separate package. There are currently three:\n", "* [micm](https://github.com/NCAR/micm): ODE solvers and gas-phase chemistry package.\n", "* [tuv-x](https://github.com/NCAR/tuv-x)**: Photolysis rate constant calculator.\n", "* [carma](https://github.com/ESCOMP/CARMA)**: Aerosol model. \n", "\n", ">__NOTE:__ The unreliability of fortran compilers prevents inclusion of `tuv-x` and `carma` in `musica` on some systems (including Windows). Some of the cells below will fail if you are on one these systems. As legacy code is ported to modern languages, we expect to be able to include all `musica` components in all deployments." ] }, { "cell_type": "code", "execution_count": 28, "id": "712294e2-7822-4cf0-892e-712a6c21bdde", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "3.12.0\n" ] } ], "source": [ "print(musica.micm.__version__)" ] }, { "cell_type": "code", "execution_count": 29, "id": "20818f97-8f2d-483d-a91e-c993dfd424d2", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0.15.0\n" ] } ], "source": [ "print(musica.tuvx.__version__)" ] }, { "cell_type": "code", "execution_count": 30, "id": "c104c9ea-778e-442e-a58b-a5e262a89d2c", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "4.0.0\n" ] } ], "source": [ "print(musica.carma.__version__)" ] }, { "cell_type": "markdown", "id": "781a749b-dcc6-43c4-8dd9-b561efb4e25c", "metadata": {}, "source": [ "To run chemistry you only need `micm`, which is our ODE solver. There are separate tutorials for `tuv-x` and `carma` so we will forget about them for now.\n", "\n", "## A simple gas-phase system from a configuration" ] }, { "cell_type": "markdown", "id": "887a203e-dd11-4492-bcac-c265e701921a", "metadata": {}, "source": [ "There are two ways to configure `musica`:\n", "* using a valid [mechanism configuration](https://mechanismconfiguration.readthedocs.io/en/latest/v1/index.html) in json/yaml\n", "* using the `musica` API.\n", "\n", "We'll use the configuration file approach in this first tutorial chapter. In the next chapter we'll present the in-code approach, which we'll use in the remaining examples.\n", "\n", "We've provided several packaged example configurations with musica to make their usage easy. We will run a simple 3-species system below. We'll start by importing the required parts of musica. \n", "\n", "- `musica`, this will give us access to all parts of musica, including `MICM`, our gas-phase solver\n", "- `Parser`, which can read our mechanism files\n", "- `find_config_path`, a utility function that will make it easy to load our example" ] }, { "cell_type": "code", "execution_count": 31, "id": "84ca8ccd-e60a-4fd4-9422-1c98fd8b06a5", "metadata": {}, "outputs": [], "source": [ "import musica\n", "from musica.mechanism_configuration import Parser\n", "from musica.utils import find_config_path" ] }, { "cell_type": "markdown", "id": "02c37997", "metadata": {}, "source": [ "Before we build our model, let's take a look at the configuration file to see how it's organized" ] }, { "cell_type": "code", "execution_count": 32, "id": "0aeefee9", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", " \"name\": \"analytical test\",\n", " \"reactions\": [\n", " {\n", " \"type\": \"ARRHENIUS\",\n", " \"A\": 0.004,\n", " \"reactants\": [\n", " {\n", " \"species name\": \"A\",\n", " \"coefficient\": 1.0\n", " }\n", " ],\n", " \"products\": [\n", " {\n", " \"species name\": \"B\",\n", " \"coefficient\": 1.0\n", " }\n", " ],\n", " \"gas phase\": \"gas\"\n", " },\n", " {\n", " \"type\": \"ARRHENIUS\",\n", " \"A\": 0.004,\n", " \"reactants\": [\n", " {\n", " \"species name\": \"B\",\n", " \"coefficient\": 1.0\n", " }\n", " ],\n", " \"products\": [\n", " {\n", " \"species name\": \"C\",\n", " \"coefficient\": 1.0\n", " }\n", " ],\n", " \"gas phase\": \"gas\"\n", " }\n", " ],\n", " \"species\": [\n", " {\n", " \"name\": \"A\"\n", " },\n", " {\n", " \"name\": \"B\"\n", " },\n", " {\n", " \"name\": \"C\"\n", " }\n", " ],\n", " \"phases\": [\n", " {\n", " \"name\": \"gas\",\n", " \"species\": [\n", " {\n", " \"name\": \"A\"\n", " },\n", " {\n", " \"name\": \"B\"\n", " },\n", " {\n", " \"name\": \"C\"\n", " }\n", " ]\n", " }\n", " ],\n", " \"version\": \"1.0.0\"\n", "}\n" ] } ], "source": [ "import json\n", "\n", "with open(find_config_path(\"v1\", \"analytical\", \"config.json\")) as f:\n", " config = json.load(f)\n", "\n", "print(json.dumps(config, indent=2))" ] }, { "cell_type": "markdown", "id": "2ffd3143", "metadata": {}, "source": [ "This simple mechanism comprises:\n", "* Three species (`A`, `B`, and `C`) in the gas-phase\n", "* Two Arrhenius reactions\n", " * `A -> B`\n", " * `B -> C`\n", "\n", "The format of the `musica` configuration is intended to describe the science without introducing details of how `musica` software is implemented in code.\n", "\n", "Now, let's build a real model" ] }, { "cell_type": "code", "execution_count": 33, "id": "029d6499-0a66-451e-ad77-a1a87eb59642", "metadata": {}, "outputs": [], "source": [ "parser = Parser()\n", "mechanism = parser.parse(find_config_path(\"v1\", \"analytical\", \"config.json\"))" ] }, { "cell_type": "code", "execution_count": 34, "id": "39195e8f-33bc-46eb-aaf3-7d36f96d8d42", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[Arrhenius] A -> B\n", "[Arrhenius] B -> C\n" ] } ], "source": [ "for reaction in mechanism.reactions:\n", " print(f\"[{reaction.type.name}] {reaction.to_equation()}\")" ] }, { "cell_type": "markdown", "id": "cb43af3d-02bc-416f-8d14-65fb62b014d8", "metadata": {}, "source": [ "Above, we had our parser read the mechanism file and return a valid mechanism. We can see the same reactions from the configuration file, now part of a `mechanism` instance.\n", "\n", "We can now use this to create a `MICM` solver and a state." ] }, { "cell_type": "code", "execution_count": 35, "id": "26b69c99-d52f-4193-ad81-a295e15a2c2a", "metadata": {}, "outputs": [], "source": [ "solver = musica.MICM(mechanism=mechanism)\n", "state = solver.create_state()" ] }, { "cell_type": "markdown", "id": "f5c0fb2e-298b-4bdb-9641-4c40b277ed88", "metadata": {}, "source": [ "_Why do we need a solver and a state?_\n", "\n", "Conceptually, they fill two distinct roles:\n", "- The `state` is a container for time-varying properties for your system of interest (temperature, pressure, species concentrations, etc.)\n", "- The `solver` is the logic needed to advance a `state` in time. In our case we get the default MICM solver, which is a Rosenbrock 3-stage solver with a predetermined set of solver parameters. We could get a different ODE solver, or set different solver parameters by including additional arguments to the `MICM()` constructor.\n", "\n", "Practically, having a separate solver and state allows you to create as many `state` containers as you want. Each state can all be advanced in time using the same `solver` instance. Each `state` instance can also be configured to represent multiple grid cells, which you'll read more about in the next tutorial.\n", "\n", "For now, we'll keep things simple with a single one-grid-cell `state`. Let's set the initial conditions and move the chemistry forward!" ] }, { "cell_type": "code", "execution_count": 36, "id": "341bf8d8-1f75-4b4e-bb4a-bc2990ba4cd3", "metadata": {}, "outputs": [], "source": [ "state.set_conditions(temperatures=[298.15], pressures=[101325])\n", "state.set_concentrations(\n", " {\n", " 'A' : [1.0],\n", " 'B' : [0.0]\n", " }\n", ")" ] }, { "cell_type": "markdown", "id": "0010d2c2-8d75-4be5-b844-f7b14982c37b", "metadata": {}, "source": [ "Now all that's left is to solve and collect the concentrations as they change over time. For now, we'll print them out. Later tutorials have more code to make some pretty graphs.\n", "\n", "`solver.solve` takes in a state and timestep to solve for. Let's solve for 10 seconds and see how things change" ] }, { "cell_type": "code", "execution_count": 37, "id": "643cafc9-2576-4bdc-823f-565970b26eb3", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0\t| 1.0000\t| 0.0000\n", "1\t| 0.9960\t| 0.0040\n", "2\t| 0.9920\t| 0.0079\n", "3\t| 0.9881\t| 0.0119\n", "4\t| 0.9841\t| 0.0157\n", "5\t| 0.9802\t| 0.0196\n", "6\t| 0.9763\t| 0.0234\n", "7\t| 0.9724\t| 0.0272\n", "8\t| 0.9685\t| 0.0310\n", "9\t| 0.9646\t| 0.0347\n", "10\t| 0.9608\t| 0.0384\n" ] } ], "source": [ "def print_concentrations(t, state):\n", " cs = state.get_concentrations()\n", " print(f\"{t}\\t| {cs['A'][0]:.4f}\\t| {cs['B'][0]:.4f}\")\n", "\n", "print_concentrations(0, state)\n", "for t in range(1, 11):\n", " solver.solve(state, 1)\n", " print_concentrations(t, state)" ] }, { "cell_type": "markdown", "id": "f6d0ceef-7ac0-48a2-9b68-150178eba502", "metadata": {}, "source": [ "And that's your first MUSICA box model! Read on to learn how to define mechanisms in-code for solving and to do more some more complex tasks." ] } ], "metadata": { "kernelspec": { "display_name": ".venv (3.12.3)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.3" } }, "nbformat": 4, "nbformat_minor": 5 }